Kotlin-Mvvm-DaggerHilt-Retrofit-Download Image Using Url-Example

 Kotlin-Mvvm-DaggerHilt-Retrofit-Download Image Using Url-Example

Step 1:
Open build.gradle(:Project) and add following dependency and rebuild the project.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}
Step 2:
Open build.gradle(:app) and add following dependency and rebuild the project.
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdk 31
defaultConfig {
applicationId "in.kotlinkatta.kotlinkattademo"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}

buildFeatures{
dataBinding true;
viewBinding true
}
}

dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
//kotlin Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
//room
implementation "androidx.room:room-ktx:2.4.0"
implementation "androidx.room:room-runtime:2.4.0"
kapt "androidx.room:room-compiler:2.4.0"
//multidex
implementation 'androidx.multidex:multidex:2.0.1'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
// view model ktx
implementation 'androidx.activity:activity-ktx:1.4.0'
//hilt viewmodel
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
kapt("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.3.0")
//retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
//moshi
implementation("com.squareup.moshi:moshi-kotlin:1.12.0")
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
//SharedPreferences
implementation("androidx.datastore:datastore:1.0.0")
implementation("androidx.datastore:datastore-preferences:1.0.0")
//DaggerHilt
implementation 'com.google.dagger:hilt-android:2.40.5'
kapt 'com.google.dagger:hilt-compiler:2.40.5'
}
// Allow references to generated code
kapt {
correctErrorTypes = true
}
Step 3:
Open gradle.properties and add following code and rebuild the project.
kapt.use.worker.api=false
Step 4:
Open AndroidManifest.xml and add following dependency and rebuild the project.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="in.kotlinkatta.kotlinkattademo">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".container.BaseApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.KotlinKattaDemo">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
App Flow Image:
Step 5:
Create new class BaseApp.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.container

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class BaseApp : Application() {}
Step 6:
Create new interface ApiServices.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.network

import okhttp3.ResponseBody
import retrofit2.http.GET
import retrofit2.http.Url

interface ApiServices {
companion object {
const val BASE_URL = "https://www.zidsworld.com"
}

@GET
suspend fun getDownloadImageDetails(@Url fileUrl: String): ResponseBody
}
Step 7:
Create new class ApiServicesImp.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.network

import okhttp3.ResponseBody
import retrofit2.http.Url
import javax.inject.Inject

class ApiServicesImp @Inject constructor(private val apiServices: ApiServices) {

suspend fun getDownloadImageDetails(@Url fileUrl: String): ResponseBody = apiServices.getDownloadImageDetails(fileUrl)
}
Step 8:
Create new class NetworkHelper.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.network

import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class NetworkHelper @Inject constructor(@ApplicationContext private val context: Context) {
fun isNetworkConnected(): Boolean {
var result = false
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val networkCapabilities = connectivityManager.activeNetwork ?: return false
val activeNetwork =
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
result = when {
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
connectivityManager.run {
connectivityManager.activeNetworkInfo?.run {
result = when (type) {
ConnectivityManager.TYPE_WIFI -> true
ConnectivityManager.TYPE_MOBILE -> true
ConnectivityManager.TYPE_ETHERNET -> true
else -> false
}
}
}
}
return result
}
}
Step 9:
Create new object AppModule.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.di

import `in`.kotlinkatta.kotlinkattademo.network.ApiServices
import `in`.kotlinkatta.kotlinkattademo.network.ApiServicesImp
import `in`.kotlinkatta.kotlinkattademo.repository.ImageDownloadRepository
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun providesMoshi(): Moshi = Moshi
.Builder()
.add(KotlinJsonAdapterFactory())
.build()

@Provides
@Singleton
fun providesApiService(moshi: Moshi): ApiServices = Retrofit
.Builder()
.run {
baseUrl(ApiServices.BASE_URL)
addConverterFactory(MoshiConverterFactory.create(moshi).asLenient())
.build()
}.create(ApiServices::class.java)

@Provides
fun providesImageDownloadRepository(apiServicesImp: ApiServicesImp): ImageDownloadRepository =
ImageDownloadRepository(apiServicesImp)
}
Step 10:
Create new sealed class ApiStateImageDownload.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.apistate

import okhttp3.ResponseBody

sealed class ApiStateImageDownload {
class Success(val data: ResponseBody) : ApiStateImageDownload()
class Failure(val errormsg: Throwable) : ApiStateImageDownload()
object Loading : ApiStateImageDownload()
object Empty : ApiStateImageDownload()
class Message(val msg: String) : ApiStateImageDownload()
}
Step 11:
Create new class ImageDownloadRepository.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.repository

import `in`.kotlinkatta.kotlinkattademo.network.ApiServicesImp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import okhttp3.ResponseBody
import retrofit2.http.Url
import javax.inject.Inject

class ImageDownloadRepository @Inject constructor(
private val apiServicesImp: ApiServicesImp
) {
fun downloadImageDetails(@Url fileUrl: String): Flow<ResponseBody> = flow {
emit(apiServicesImp.getDownloadImageDetails(fileUrl))
}.flowOn(Dispatchers.IO)
}
Step 12:
Create new class ImageDownloadViewModel.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.uiviewmodel

import `in`.kotlinkatta.kotlinkattademo.apistate.ApiStateImageDownload
import `in`.kotlinkatta.kotlinkattademo.network.NetworkHelper
import `in`.kotlinkatta.kotlinkattademo.repository.ImageDownloadRepository
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import retrofit2.http.Url
import javax.inject.Inject

@HiltViewModel
class ImageDownloadViewModel
@Inject
constructor(
private val imageDownloadRepository: ImageDownloadRepository,
private val networkHelper: NetworkHelper
) : ViewModel() {

private val _imageDownloadApiState: MutableStateFlow<ApiStateImageDownload> = MutableStateFlow(
ApiStateImageDownload.Empty
)
val imageDownloadApiState: StateFlow<ApiStateImageDownload> = _imageDownloadApiState

fun postImageDownloadDetails(@Url fileUrl: String) = viewModelScope.launch {
if (networkHelper.isNetworkConnected()) {
imageDownloadRepository.downloadImageDetails(fileUrl)
.onStart {
_imageDownloadApiState.value = ApiStateImageDownload.Loading
}.catch { e ->
_imageDownloadApiState.value = ApiStateImageDownload.Failure(e)
}.collect { data ->
_imageDownloadApiState.value = ApiStateImageDownload.Success(data)
}
} else {
_imageDownloadApiState.value = ApiStateImageDownload.Message("No internet connection")
}
}
}
Step 13:
Open layout file activity_main.kt and add following code.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<ImageView
android:id="@+id/imgDisplay"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ProgressBar
android:id="@+id/progressbarProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
Step 14:
Open Activity class MainActivity.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo

import `in`.kotlinkatta.kotlinkattademo.apistate.ApiStateImageDownload
import `in`.kotlinkatta.kotlinkattademo.databinding.ActivityMainBinding
import `in`.kotlinkatta.kotlinkattademo.uiviewmodel.ImageDownloadViewModel
import android.graphics.BitmapFactory
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect

@AndroidEntryPoint
public class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding
private val imageDownloadViewModel: ImageDownloadViewModel by viewModels()
var imageUrl = "/wp-content/uploads/2018/06/cat_1530281469.jpg"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.progressbarProgress.isVisible = false
getPostData()
}
private fun getPostData() {
binding.apply {
progressbarProgress.isVisible = true

lifecycleScope.launchWhenStarted {
imageDownloadViewModel.postImageDownloadDetails(imageUrl)
imageDownloadViewModel.imageDownloadApiState.collect {
when (it) {
is ApiStateImageDownload.Success -> {
Log.d("main", "${it.data}")
progressbarProgress.isVisible = false
imgDisplay.isVisible = true
val bytes: ByteArray = it.data.bytes()
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
imgDisplay.setImageBitmap(bitmap)
}
is ApiStateImageDownload.Failure -> {
progressbarProgress.isVisible = false
Toast.makeText(this@MainActivity, it.errormsg.toString(), Toast.LENGTH_SHORT).show()
}
is ApiStateImageDownload.Loading -> {
progressbarProgress.isVisible = true
}
is ApiStateImageDownload.Empty -> {
progressbarProgress.isVisible = false
}
is ApiStateImageDownload.Message -> {
progressbarProgress.isVisible = false
Toast.makeText(this@MainActivity, it.msg, Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
}
Output:

Comments